#include "StdAfx.h"
#include "SocketsConnection.h"

using namespace Adaptive::Win32;
using namespace Adaptive::Network;
using namespace Adaptive::Network::Sockets;
using namespace System::Text;

/*--------------------------------------------------------------------------------
 * Constructor / Dispose / Destructor Methods
 *------------------------------------------------------------------------------*/
#pragma region Constructor / Dispose / Destructor Methods
// Default initializer.
SocketsConnection::SocketsConnection(SOCKET socketHandle) : DisposableObjectBase()
{
	_threadLock = new CRITICAL_SECTION();
	::InitializeCriticalSection(_threadLock);

	_socketHandle = gcnew SafeSocketHandle(socketHandle);
	LaunchPollingThread();
	_remoteEndPoint = Win32Socket::GetRemoteHostAddress(_socketHandle);
	_localEndPoint = Win32Socket::GetLocalHostAddress(_socketHandle);
	_suspendThreadReading = false;

}
// Default initializer.
SocketsConnection::SocketsConnection(IntPtr socketHandle) : DisposableObjectBase()
{
	_threadLock = new CRITICAL_SECTION();
	::InitializeCriticalSection(_threadLock);

	_socketHandle = gcnew SafeSocketHandle(socketHandle);
	LaunchPollingThread();
	_remoteEndPoint = Win32Socket::GetRemoteHostAddress(_socketHandle);
	_localEndPoint = Win32Socket::GetLocalHostAddress(_socketHandle);
	_suspendThreadReading = false;

}
// Default initializer.
SocketsConnection::SocketsConnection(SafeSocketHandle^ socketHandle) : DisposableObjectBase()
{
	_threadLock = new CRITICAL_SECTION();
	::InitializeCriticalSection(_threadLock);

	_socketHandle = gcnew SafeSocketHandle(socketHandle->DangerousGetHandle().ToInt32());
	LaunchPollingThread();
	_remoteEndPoint = Win32Socket::GetRemoteHostAddress(_socketHandle);
	_localEndPoint = Win32Socket::GetLocalHostAddress(_socketHandle);
	_suspendThreadReading = false;

}
// Class destructor.
SocketsConnection::~SocketsConnection()
{
	if (!Disposed)
	{
		Close();
		::DeleteCriticalSection(_threadLock);
		_threadLock = NULL;
	}
	Win32Socket::ShutdownWinsock();
	_socketHandle = nullptr;
	_dataPollingThread = nullptr;
	_remoteEndPoint = nullptr;
	_localEndPoint = nullptr;
	_threadExecute = false;
}
// Class destructor.
SocketsConnection::!SocketsConnection()
{
	if (_threadLock != NULL)
		::DeleteCriticalSection(_threadLock);

	_threadLock = NULL;
	_socketHandle = nullptr;
	_dataPollingThread = nullptr;
	_remoteEndPoint = nullptr;
	_localEndPoint = nullptr;
	_threadExecute = false;
}

#pragma endregion

/*--------------------------------------------------------------------------------
 * Public Properties
 *------------------------------------------------------------------------------*/
#pragma region Public Properties
bool SocketsConnection::Connected::get()
{
	return ((_socketHandle != nullptr) && (!_socketHandle->IsInvalid) && (!_socketHandle->IsClosed));
}
bool SocketsConnection::HasData::get()
{
	bool returnValue = false;
	int byteCount = 0;

	if (Connected)
	{
		byteCount = Win32Socket::GetPendingReadByteCount(_socketHandle);
		returnValue = (byteCount > 0);
	}
	return returnValue;
}
String^ SocketsConnection::Id::get()
{
	if ((_socketHandle != nullptr) && (_socketHandle->IsClosed) && (!_socketHandle->IsInvalid))
		return _socketHandle->HandleValue.ToString();
	else
		return String::Empty;
}

Byte SocketsConnection::ReadByte()
{
	Byte returnValue = 0;

	if ((Connected) && (HasData))
	{
		returnValue = (Byte)Win32Socket::RecieveByte(_socketHandle, SocketRecieveFlags::NoFlags);
	}
	return returnValue;
}
array<Byte>^ SocketsConnection::ReadAllBytes()
{
	array<Byte>^ returnValue = nullptr;
	
	if ((Connected) && (HasData))
	{
		returnValue = TransformAfterReceive( Win32Socket::Recieve(_socketHandle, SocketRecieveFlags::NoFlags) );
	}
	return returnValue;
}	
array<Byte>^ SocketsConnection::ReadBytes(int numberOfBytes)
{
	array<Byte>^ returnValue = nullptr;
	
	if ((Connected) && (HasData))
	{
		returnValue = TransformAfterReceive( Win32Socket::Recieve(_socketHandle, SocketRecieveFlags::NoFlags) );
	}
	return returnValue;
}
#pragma endregion

/*--------------------------------------------------------------------------------
 * Public Methods / Functions
 *------------------------------------------------------------------------------*/
#pragma region Public Methods / Functions
// Close the connection.
void SocketsConnection::Close()
{
	if (_socketHandle != nullptr)
	{
		::EnterCriticalSection(_threadLock);

		//Stop thread.
		StopPollingThread();

		//End socket.
		Win32Socket::ShutdownSocket(_socketHandle, SocketShutdown::Both);
		Win32Socket::CloseSocket(_socketHandle);
		_socketHandle->Close();

		delete(_socketHandle);
		_socketHandle = nullptr;

		if (_remoteEndPoint != nullptr)
			delete(_remoteEndPoint);
		if (_localEndPoint != nullptr)
			delete(_localEndPoint);

		_remoteEndPoint = nullptr;
		_localEndPoint = nullptr;

		::LeaveCriticalSection(_threadLock);
		OnDisconnected(EventArgs::Empty);
	}
}
String^ SocketsConnection::ReadString()
{
	array<Byte>^ dataBuffer = nullptr;
	String^ returnValue = nullptr;

	if ((Connected) && (HasData))
	{
		dataBuffer = Win32Socket::Recieve(_socketHandle, SocketRecieveFlags::WaitForAll);
		if ((dataBuffer != nullptr) && (dataBuffer->Length > 0))
			returnValue = System::Text::ASCIIEncoding::ASCII->GetString(dataBuffer);
		delete(dataBuffer);
	}
	return returnValue;
}
int SocketsConnection::Send(char dataToSend)
{
	array<Byte>^ dataBuffer = nullptr;
	int returnValue = 0;

	if (Connected)
	{
		dataBuffer = gcnew array<Byte>(1);
		dataBuffer[0] = (unsigned char)dataToSend;

		returnValue = Send(dataBuffer);

		delete(dataBuffer);
	}
	return returnValue;
}
int SocketsConnection::Send(unsigned char dataByte)
{
	array<Byte>^ dataBuffer = nullptr;
	int returnValue = 0;

	if (Connected)
	{
		dataBuffer = gcnew array<Byte>(1);
		dataBuffer[0] = (unsigned char)dataByte;

		returnValue = Send(dataBuffer);

		delete(dataBuffer);
	}
	return returnValue;
}
int SocketsConnection::Send(int dataToSend)
{
	int returnValue = 0;
	BYTE *rawData = (BYTE*)&dataToSend;
	array<Byte>^ dataBuffer = gcnew array<Byte>(4);
	for(int count = 0; count < 4; count++)
		dataBuffer[count] = (Byte)rawData[count];

	returnValue = Send(dataBuffer);

	delete(dataBuffer);
	return returnValue;
}
int SocketsConnection::Send(short dataToSend)
{
	int returnValue = 0;
	BYTE *rawData = (BYTE*)&dataToSend;
	array<Byte>^ dataBuffer = gcnew array<Byte>(2);
	for(int count = 0; count < 2; count++)
		dataBuffer[count] = (Byte)rawData[count];

	returnValue = Send(dataBuffer);

	delete(dataBuffer);
	return returnValue;
}
int SocketsConnection::Send(__int64 dataToSend)
{
	int returnValue = 0;
	BYTE *rawData = (BYTE*)&dataToSend;
	array<Byte>^ dataBuffer = gcnew array<Byte>(8);
	for(int count = 0; count < 8; count++)
		dataBuffer[count] = (Byte)rawData[count];

	returnValue = Send(dataBuffer);

	delete(dataBuffer);
	return returnValue;
}
int SocketsConnection::Send(array<Byte>^ dataToSend)
{
	int returnValue = 0;

	if (Connected)
	{
		OnDataSending(EventArgs::Empty);
		dataToSend = TransformBeforeSend(dataToSend);
		returnValue = Win32Socket::Send(_socketHandle, dataToSend, SocketSendFlags::NoFlags);
		if (returnValue == dataToSend->Length)
			OnDataSent(gcnew DataSentEventArgs(dataToSend));
	}
	return returnValue;
}
int SocketsConnection::Send(String^ dataToSend)
{
	int returnValue = 0;

	returnValue = Send(dataToSend, true);
	return returnValue;
}
int SocketsConnection::Send(String^ dataToSend, bool sendInAscii)
{
	array<Byte>^ dataBuffer = nullptr;
	int returnValue = 0;

	if (sendInAscii)
		dataBuffer = System::Text::ASCIIEncoding::ASCII->GetBytes(dataToSend);
	else
		dataBuffer = System::Text::UnicodeEncoding::Unicode->GetBytes(dataToSend);

	returnValue = Send(dataBuffer);
	delete(dataBuffer);
	return returnValue;
}
array<Byte>^ SocketsConnection::WaitForData(array<Byte>^ dataToWaitFor, int timeOutValue)
{
	StringBuilder^ builder = nullptr;
	array<Byte>^ content = nullptr;
	clock_t start = 0;
	clock_t end = 0;
	String ^dataContent = System::Text::Encoding::ASCII->GetString(dataToWaitFor);

	builder = gcnew StringBuilder();
	_suspendThreadReading = true;
	start = clock();
	do
	{
		if (HasData)
		{
			content = ReadAllBytes();
			builder->Append(System::Text::Encoding::ASCII->GetString(content));
		}
		end = clock();
	} while ((end - start < (timeOutValue*1000)) && (!builder->ToString()->Contains(dataContent)));
	
	content = System::Text::Encoding::ASCII->GetBytes(builder->ToString());

	_suspendThreadReading = false;
	return content;
}
String^ SocketsConnection::WaitForStringData(String ^dataToWaitFor, int timeOutValue)
{
	StringBuilder^ builder = nullptr;
	array<Byte>^ content = nullptr;
	clock_t start = 0;
	clock_t end = 0;

	builder = gcnew StringBuilder();
	_suspendThreadReading = true;
	start = clock();
	do
	{
		if (HasData)
		{
			content = ReadAllBytes();
			builder->Append(System::Text::Encoding::ASCII->GetString(content));
		}
		end = clock();
	} while ((end - start < (timeOutValue*1000)) && (!builder->ToString()->Contains(dataToWaitFor)));

	_suspendThreadReading = false;
	return builder->ToString();
}
String^ SocketsConnection::SendAndReceive(String^ dataToSend, String^ dataToWaitFor, int timeOutValue)
{
	String^ returnValue = nullptr;
	int result = 0;

	_suspendThreadReading = true;
	Sleep(10);
	result = Send(dataToSend);
	if (result == dataToSend->Length)
	{
		returnValue = WaitForStringData(dataToWaitFor, timeOutValue);
	}

	_suspendThreadReading = false;
	return returnValue;

}
#pragma endregion

/*--------------------------------------------------------------------------------
 * Event Methods
 *------------------------------------------------------------------------------*/
#pragma region Event Methods

void SocketsConnection::OnConnectionEstablished(EventArgs^ e)
{
	ConnectionEstablished(this, e);
}
void SocketsConnection::OnDataReceived(DataReceivedEventArgs^ e)
{
	DataReceived(this, e);
}
void SocketsConnection::OnDataSending (EventArgs^ e)
{
	DataSending(this, e);
}
void SocketsConnection::OnDataSent(DataSentEventArgs^ e)
{
	DataSent(this, e);
}
void SocketsConnection::OnDisconnected(EventArgs^ e)
{
	Disconnected(this, e);
}
void SocketsConnection::OnSocketException (SocketErrorEventArgs^ e)
{
	SocketException(this, e);
}
#pragma endregion

/*--------------------------------------------------------------------------------
 * Protected Methods / Functions
 *------------------------------------------------------------------------------*/
#pragma region Protected Methods / Functions

array<Byte>^ SocketsConnection::TransformBeforeSend(array<Byte>^ originalData)
{
	return originalData;
}
array<Byte>^ SocketsConnection::TransformAfterReceive(array<Byte>^ originalData)
{
	return originalData;
}

#pragma endregion

/*--------------------------------------------------------------------------------
 * Private Methods / Functions
 *------------------------------------------------------------------------------*/
#pragma region Private Methods / Functions

IPAddress^ SocketsConnection::ResolveHostName(String^ originalName)
{
	array<Dns::ManagedDnsRecord^>^ resultList = nullptr;
	IPAddress^	returnValue = nullptr;
	int itemCount = 0;
	int itemLen = 0;

	resultList = Dns::DnsQuery::PerformDnsQuery(originalName, Dns::DnsQueryType::Address, 
		Dns::DnsQueryOption::Standard | Dns::DnsQueryOption::OnTheWireOnly);

	if ((resultList != nullptr) && (resultList->Length > 0))
	{
		itemLen = resultList->Length;
		itemCount = 0;
		while ((itemCount < itemLen) && (resultList[itemCount]->AddressRecord.IP == nullptr))
			itemCount++;

		if (resultList[itemCount]->AddressRecord.IP != nullptr)
			returnValue = gcnew IPAddress( resultList[itemCount]->AddressRecord.IP->Address );
	}

	delete(resultList);

	return returnValue;
}

void SocketsConnection::LaunchPollingThread()
{
	ThreadStart^ functionPtr = nullptr;

	if (_dataPollingThread == nullptr)
	{
		_threadExecute = true;
		functionPtr = gcnew ThreadStart(this, &SocketsConnection::ExecutePollingThread);
		_dataPollingThread = gcnew Thread(functionPtr);
		_dataPollingThread->IsBackground = true;
		_dataPollingThread->Start();
	}
}
void SocketsConnection::StopPollingThread()
{
	clock_t startTime = 0;
	clock_t endTime = 0;

	_threadExecute = false;
	startTime = clock();
	endTime = clock();
	while ((_dataPollingThread != nullptr) && (_dataPollingThread->IsAlive) && (endTime - startTime < 30000))
	{
		Adaptive::Win32::Kernel32::Sleep(500);
		endTime = clock();
	}

	if ((_dataPollingThread != nullptr) && (_dataPollingThread->IsAlive))
	{
		try
		{
			_dataPollingThread->Abort();
		}
		catch(...)
		{
		}
	}
	_dataPollingThread = nullptr;

}
void SocketsConnection::ExecutePollingThread()
{
	array<Byte>^ buffer = nullptr;
	int byteCount = 0;

	while ((_threadExecute) && (_socketHandle != nullptr) && (!_socketHandle->IsClosed) && (!_socketHandle->IsInvalid))
	{
		byteCount = Win32Socket::GetPendingReadByteCount(_socketHandle);
		if (byteCount > 0)
		{
			//Read data.
			if (!_suspendThreadReading)
			{
				if (HasData)
				{
					if (_threadLock->LockCount <= 0)
						::EnterCriticalSection(_threadLock);

					buffer = ReadAllBytes();
					::LeaveCriticalSection(_threadLock);

					if (buffer != nullptr)
					{
						buffer = TransformAfterReceive(buffer);
						OnDataReceived(gcnew DataReceivedEventArgs(buffer));
					}
				}
			}
		}
		//Sleep for a bit.
		System::Threading::Thread::Sleep(10);
	}
}
#pragma endregion